# -*- coding: utf-8 -*-

# 定义一个类
"""
class Test:
    '''example class'''

    x = 'first' # 类属性

    def Fir(self):  # 类方法
        return 'This is a first class'

print(Test.x)
print(Test.Fir)
print(Test.__doc__)

# 在Test中,x,Fir，__doc__都是类的属性
a = Test(7)  # 实例化
"""


# 实例化
"""
class First:
    def __init__(self):
        print('init')
    
print(First) # 不会调用
print(First())  # 调用__init__
b = First()  # 调用__init__
"""

"""
Class Xiao:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def active(self):
        print('{} is {}'.format(self.name,self.age))

d = Xiao('jing',30)  # 实例化
e = Xiao('john',27)
print(d.name,e.age)
e.age+= 1
print(e.age)
e.active()
"""

'''
class MyClass:
    def __init__(self):
        print('init')

print(MyClass) # 不会调用
print(MyClass())  # 调用__init__
a = MyClass() # 调用__init__
'''


# 初始化函数可以多个参数,请注意第一个位置必须是self
"""
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def showage(self):
        print('{} is {}'.format(self.name,self.age))

tom = Person('Tom',20) # 实例化
jerry = Person('Je',25)
print(tom.name,jerry.age)
jerry.age += 1
print(jerry.age)
jerry.showage()
'''
类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时采用jerry.showage()的方式. 但是函数签名是showage(self),少传一个参数self吗?

这个self就是jerry,Python会把方法的调用者作为第一参数self的实参传入.  self.name就是jerry对象的name,name是保存在了jerry对象上,而不是Person类上,所以,称为实例变量
'''
"""




# 2. self
"""
class MyClass:
    def __init__(self):
        print('self in init = {}'.format(id(self)))

c = MyClass() # 会调用__init__
print('c = {}'.format(id(c)))

# 打印结果为:
# self in init = 139856523358952
# c = 139856523358952
'''
self就是调用者,就是c对应的实例对象
self这个名字是一个惯例,建议不要修改.
'''
"""

# 3. 实例变量和类变量
"""
class Person:
    age = 3
    def __init__(self,name):
        self.name = name

# tom = Person('Tom',20)
tom = Person('Tom') # 实例化,初始化
jerry = Person('Jerry')

print(tom.name,tom.age)
print(jerry.name,jerry.age)
print(Person.age)
Person.age = 30
print(Person.age,tom.age,jerry.age)
'''
运行结果:
Tom 3
Jerry 3
3
30 30 30
'''


class Person:
    age = 3

    def __init__(self,name):
        self.name = name

print('------class-------')
print(Person.__class__)
print(sorted(Person.__dict__.items()),end='\n\n') # 属性字典

tom = Person('Tom')
print('-----instance tom---------')
print(tom.__class__)
print(sorted(tom.__dict__.items()),end='\n\n')

print("--------tom's class--------")
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end='\n\n')

'''
类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,
如果从实例访问类的属性,就需要借助__class__找到所属的类.
有了上面知识,再看下面的代码:
'''
"""


"""
class Person:
    age = 3
    height = 170

    def __init__(self,name,age=18):
        self.name = name
        self.age = age

tom = Person('Tom') # 实例化,初始化
jerry = Person('Jerry',20)

Person.age = 30
print(Person.age,tom.age,jerry.age)  # 输出什么结果
print(Person.height,tom.height,jerry.height) # 输出什么结果

jerry.height = 175
print(Person.height,tom.height,jerry.height) # 输出什么结果

tom.height += 10
print(Person.height,tom.height,jerry.height) # 输出什么结果

Person.height += 15
print(Person.height,tom.height,jerry.height) # 输出什么结果

Person.weight = 70
print(Person.weight,tom.weight,jerry.weight) # 输出什么结果

print(tom.__dict__['height'])
print(tom.__dict__['weight']) # 可以嘛?
'''
self in init = 140457647524272
c = 140457647524272
30 18 20
170 170 170
170 170 175
170 180 175
185 180 175
70 70 70
180
Traceback (most recent call last):
  File "duixiang.py", line 183, in <module>
    print(tom.__dict__['weight']) # 可以嘛?
KeyError: 'weight'
'''

'''
总结:
是类的,也是这个类所有实例的,其实例都可以访问到; 是实例的,就是这个实例自己的,通过类访问不到.
类变量是属于类的变量,这个类的所有实例可以共享这个变量.
'''
"""


#### 装饰一个类
"""
'''
回顾: 什么是高阶函数? 什么是装饰器函数?
思考: 如何装饰一个类
需求: 为一个类通过装饰,增加一些类属性
'''

# 增加类变量
def add_name(name,cls):
    cls.NAME = name  # 动态增加类属性

# 改进成装饰器
def add_name(name):
    def wrapper(cls):
        cls.NAME = name
        return cls
    return wrapper

@add_name('Tom')
class Person:
    AGE = 3

print(Person.NAME)
'''
之所以能够装饰,本质上是为类对象动态的添加了一个属性,而Person这个标识符指向这个类对象.
'''
"""

#### 类方法和静态方法 

##### 普通函数
"""
class Person:
    def normal_method(): # 可以嘛?
        print('normal')

# 如何调用
Person.normal_method() # 可以嘛?
Person().normal_method() # 可以嘛?

print(Person.__dict__)

'''
normal
Traceback (most recent call last):
  File "duixiang.py", line 249, in <module>
    Person().normal_method() # 可以嘛?
TypeError: normal_method() takes 0 positional arguments but 1 was given

Person.normal_method() 
可以,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method这是Person的一个属性而已.
由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用.
注意: 语法是对的,但是禁止这么写:Person().normal_method()
'''
"""

##### 类方法
"""
class Person:
    @classmethod
    def class_method(cls): # cls是什么
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

Person.class_method()
print(Person.__dict__)

'''
class = Person (<class '__main__.Person'>)
{'__module__': '__main__', 'class_method': <classmethod object at 0x7fbb2cf7a2e8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__':<attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
'''
"""

##### 静态方法
"""
class Person:
    @classmethod
    def class_method(cls): # cls 是什么
        print('class={0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

    @staticmethod
    def static_methd():
        print(Person.HEIGHT)

Person.class_method()
Person.static_methd()
print(Person.__dict__)

'''
class = Person (<class '__main__.Person'>)
{'__module__': '__main__', 'class_method': <classmethod object at 0x7f49d82cb
8d0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__':
<attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
class=Person (<class '__main__.Person'>)
170
{'__module__': '__main__', 'class_method': <classmethod object at 0x7f49d82cba58>, 'static_methd': <staticmethod object at 0x7f49d82cb6d8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
'''
"""

##### 方法的调用
# 类可以定义这么多种方法,究竟如何调用它们?
"""
class Person:
    def normal_method():
        print('normal')

    def method(self):
        print("{}'s method".format(self))
    
    @classmethod
    def class_method(cls): # cls是什么
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170
    
    @staticmethod
    def static_methd():
        print(Person.HEIGHT)

print('~~~~~类访问')
print(1, Person.normal_method()) # 可以嘛
print(2, Person.method()) # 可以嘛
print(3, Person.class_method()) # 可以嘛
print(4, Person.static_methd()) # 可以嘛
print(Person.__dict__)
print('~~~~~实例访问')
print('tom---')
tom = Person()
print(1, tom.normal_method()) # 可以嘛
print(2, tom.method())  # 可以嘛
print(3, tom.class_method()) # 可以嘛
print(4, tom.static_methd())  # 可以嘛
print('jerry-----')
jerry = Person()
print(1, jerry.normal_method())  # 可以嘛
print(2, jerry.method()) # 可以嘛
print(3, jerry.class_method())  # 可以嘛
print(4, jerry.static_methd())  # 可以嘛

'''
~~~~~类访问
normal
1 None
Traceback (most recent call last):
  File "duixiang.py", line 333, in <module>
    print(2, Person.method()) # 可以嘛
TypeError: method() missing 1 required positional argument: 'self'
'''
"""

#### 访问控制
##### 私有属性
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.age = age

    def growup(self,i=1):
        if i > 0 and i < 150:  # 控制逻辑
            self.age += i 

p1 = Person('tom')
p1.growup(20) # 正常的范围
p1.age = 160 # 超过了范围,并绕过了控制逻辑
print(p1.age) # 可以嘛?
'''
输出为: 160
上例,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性.
Python提供了私有属性可以解决这个问题.
'''
"""

"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):
        if i > 0 and i < 150: # 控制逻辑
            self.__age += i

p1 = Person('tom')
p1.growup(20) # 正常的范围.
print(p1.__age) # 可以吗
'''
Traceback (most recent call last):
  File "duixiang.py", line 397, in <module>
    print(p1.__age) # 可以吗
AttributeError: 'Person' object has no attribute '__age'

通过实验可以看出,外部已经访问不到__age了,age根本就没有定义,更是访问不到.
那么,如何访问这个私有变量__age呢?
使用下面的代码中的方法来访问:
'''
"""

"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):
        if i > 0 and i < 150: # 控制逻辑
            self.__age += i

    def getage(self):
        return self.__age

print(Person('tom').getage())

'''
输出结果为: 18
'''
"""

##### 私有变量的本质
# 外部访问不到,能够动态增加一个__age呢?
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def growup(self,i=1):
        if i > 0 and i < 150: # 控制逻辑
            self.__age += i

    def getage(self):
        return self.__age

p1 = Person('tom')
p1.growup(20) # 正常的范围
# print(p1.__age) # 访问不到
p1.__age = 28
print(p1.__age)
print(p1.getage())
# 为什么年龄不一样? __age没有覆盖嘛?
print(p1.__dict__)
'''
28
38
{'name': 'tom', '_Person__age': 38, '__age': 28}
'''
# 秘密都在__dict__中,里面是{'__age':28,'_Person_age':38,'name':'tom'}
"""

'''
类定义时,如果声明一个实例变量时,使用双下划线,Python解释器会将其改名,转换名称为**_类名__变量名**的名称,所以用原来的名字访问不到了.
 知道了这个名字,能否直接修改呢?
'''
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    def growup(self,i=1):
        if i > 0 and i < 150: # 控制逻辑
            self.__age += i 

    def getage(self):
        return self.__age

p1 = Person('tom')
p1.growup(20)  # 正常的范围
# print(p1.__age) # 访问不到.
p1.__age = 28
print(p1.__age)
print(p1.getage())
# 为什么年龄不一样? __age没有覆盖嘛?
print(p1.__dict__)

# 直接修改私有变量
p1._Person__age = 15
print(p1.getage())
print(p1.__dict__)
'''
28
38
{'name': 'tom', '_Person__age': 38, '__age': 28}
15
{'name': 'tom', '_Person__age': 15, '__age': 28}

从上例可以看出,知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它.
'''
"""


##### 保护变量
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self._age = age

tom = Person('Tom')
print(tom._age)
print(tom.__dict__)
'''
可以看出,这个_age属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理. 这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用.
'''
"""

##### 私有方法
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self._age = age
    
    def _getname(self):
        return self.name
    
    def __getage(self):
        return self._age

tom = Person('Tom')
print(tom._getname()) # 没改名
print(tom.__getage()) # 无此属性
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person_getage())  # 改名了.
"""


### 属性装饰器
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    def age(self):
        return self.__age
    
    def set_age(self,age):
        self.__age = age

tom = Person('Tom')
print(tom.age())
tom.set_age(20)
print(tom.age())

'''
输出:
18
20
'''
"""

# 通过age和set_age方法操作属性.有没有简单的方式呢?
'''
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self,age):
        self.__age = age

    @age.deleter
    def age(self):
        # del self.__age
        print('del')

tom = Person('Tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age
'''

# 其他的写法
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age
    
    def getage(self):
        return self.__age

    def setage(self,age):
        self.__age = age

    def delage(self):
        # del self.__age
        print('del')

    age = property(getage,setage,delage,'age property')

tom = Person('Tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age

'''
输出:
18
20
del
'''
"""

# 还可以如下:
"""
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    age = property(lambda self:self.__age)

tom = Person('Tom')
print(tom.age)
"""



### 对象的销毁
import time

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age

    def __del__(self):
        print('delete {}'.format(self.name))

def test():
    tom = Person('tom')
    tom.__del__()
    tom.__del__()
    tom.__del__()
    tom.__del__()
    print('=====start=====')
    tom2 = tom
    tom3 = tom2
    print(1,'del')
    del tom
    time.sleep(3)

    print(2,'del')
    del tom2
    time.sleep(3)
    print('~~~~~~~~')

    del tom3 # 注释一下看效果.
    time.sleep(3)
    print('======end')

test()






